package iit.projects.etaxi.util;



import iit.projects.etaxi.model.exception.base.AppUncheckedException;

import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.ActionEvent;


import com.icesoft.faces.component.ext.HtmlInputText;
import com.icesoft.faces.component.selectinputtext.SelectInputText;

/**
 * General useful static utilities for working with JSF.
 * @author Duncan Mills
 * <br>
 * <i> $Id: JSFUtils.java,v 1.7 2006/01/17 15:36:32 duncan Exp $</i>
 */
public class JSFUtils {

    public static final String NO_RESOURCE_FOUND = "Missing resource: ";

    /**
	 * Return the FacesContext instance for the
	 * current request.
	 */
    protected static FacesContext context() {
		return (FacesContext.getCurrentInstance());
		
	}
    
    /**
     * Method for taking a reference to a JSF binding expression and returning
     * the matching object (or creating it).
     * @param expression
     * @return Managed object
     */
    public static Object resolveExpression(String expression) {
        Application app = context().getApplication();
        ValueBinding bind = app.createValueBinding(expression);
        return bind.getValue(context());
    }

    /**
     * Convenience method for resolving a reference to a managed bean by name 
     * and get value from SelectInputText.
     * rather than by expression.
     * @param beanName
     * @return Managed object
     */
    public static Object getManagedBeanValueFromSelectInputText(String beanName) {
    	return ((SelectInputText)getManagedBeanValue(beanName)).getValue();
    }
    
    /**
     * Convenience method for resolving a reference to a managed bean by name 
     * and get value from HtmlInputText.
     * rather than by expression.
     * @param beanName
     * @return Managed object
     */
    public static Object getManagedBeanValueFromHtmlInputText(String beanName) {
    	return ((HtmlInputText)getManagedBeanValue(beanName)).getValue();
    }
    /**
     * Convenience method for resolving a reference to a managed bean by name
     * rather than by expression.
     * @param beanName
     * @return Managed object
     */
    public static Object getManagedBeanValue(String beanName) {
        StringBuffer buff = new StringBuffer("#{");
        buff.append(beanName);
        buff.append("}");
        return resolveExpression(buff.toString());
    }

    /**
     * Method for setting a new object into a JSF managed bean.
     * Note: will fail silently if the supplied object does
     * not match the type of the managed bean
     * @param expression
     * @param newValue
     */
    public static void setExpressionValue(String expression, Object newValue) {
        Application app = context().getApplication();
        ValueBinding bind = app.createValueBinding(expression);

        //Check that the input newValue can be cast to the property type
        //expected by the managed bean. 
        //If the managed Bean expects a primitive we rely on Auto-Unboxing
        //I could do a more comprehensive check and conversion from the object 
        //to the equivilent primitive but life is too short
        Class bindClass = bind.getType(context());
        if (bindClass.isPrimitive() || bindClass.isInstance(newValue)) {
            bind.setValue(context(), newValue);
        }
    }

    /**
     * Convenience method for setting the value of a managed bean by name
     * rather than by expression.
     * @param beanName
     * @param newValue
     */
    public static void setManagedBeanValue(String beanName, Object newValue) {
        StringBuffer buff = new StringBuffer("#{");
        buff.append(beanName);
        buff.append("}");
        setExpressionValue(buff.toString(), newValue);
    }

    /**
     * Convenience method for setting the value of a managed bean by name
     * of SelectInputTest
     * rather than by expression.
     * @param beanName
     * @param newValue
     */
    public static void setManagedBeanValueOfSelectInputText(String beanName, Object newValue) {
    	SelectInputText selText = (SelectInputText)getManagedBeanValue(beanName);
    	if (selText != null) {
    		selText.resetValue();
    		selText.setValue(newValue);
    	}
    		
    }
    
    /**
     * Convenience method for setting the value of a managed bean by name
     * of HtmlInputTest
     * rather than by expression.
     * @param beanName
     * @param newValue
     */
    public static void setManagedBeanValueOfHtmlInputText(String beanName, Object newValue) {
    	HtmlInputText htmlText = (HtmlInputText)getManagedBeanValue(beanName);
    	if (htmlText != null) 
    		htmlText.setValue(newValue);
    }
    
    /**
     * Convenience method for setting Session variable.
     * @param key object key
     * @param object value to store
     */
    @SuppressWarnings("unchecked")
	public static void storeOnSession(String key, Object object) {
        Map sessionState = context().getExternalContext().getSessionMap();
        sessionState.put(key, object);
    }

    /**
     * Convenience method for setting Session variables.
     * @param key object key
     * @param object value to store
     */
    @SuppressWarnings("unchecked")
	public static void storeOnSession(Map map) {
        Map sessionState = context().getExternalContext().getSessionMap();
        sessionState.putAll(map);
    }
    
    /**
     * Convenience method for setting request variables.
     * @param key object key
     * @param object value to store
     */
    @SuppressWarnings("unchecked")
	public static void storeOnRequest(String key, Object object) {
        Map requestParams = context().getExternalContext().getRequestParameterMap();
        requestParams.put(key, object);
    }
    
    /**
     * Convenience method for getting Session variables.
     * @param key object key
     */
    @SuppressWarnings("unchecked")
	public static Object removeFromSession(String key) {
        Map sessionState = context().getExternalContext().getSessionMap();
        return sessionState.remove(key);
    }

    /**
     * Convenience method for getting request variable.
     * @param key object key
     */
    public static Object getFromRequest(String key) {
		return context().getExternalContext().getRequestParameterMap().get(key);
    }
    
    /**
     * Convenience method for getting request variables.
     * @param key object key
     */
    @SuppressWarnings("unchecked")
	public static Map getFromRequest() {
		return context().getExternalContext().getRequestParameterMap();
    }
    
    /**
     * Pulls a String resource from the property bundle that
     * is defined under the application &lt;message-bundle&gt; element in
     * the faces config. Respects Locale
     * @param key
     * @return Resource value or placeholder error String
     */
    public static String getStringFromBundle(String key) {
        ResourceBundle bundle = getBundle();
        return getStringSafely(bundle, key, null);
    }
    
    public static String getResourcesProperty(String key) {
		return getStringFromBundle(Constants.BundleNames.RESOURCES, key);
	}
    
    public static String getResourcesProperty(String key, Object[] args){
    	return getStringFromBundle(Constants.BundleNames.RESOURCES, key, args);
    }

    /**
     * Convenience method to construct a <code>FacesMesssage</code>
     * from a defined error key and severity
     * This assumes that the error keys follow the convention of
     * using <b>_detail</b> for the detailed part of the
     * message, otherwise the main message is returned for the
     * detail as well.
     * @param key for the error message in the resource bundle
     * @param severity
     * @return Faces Message object
     */
    public static FacesMessage getMessageFromBundle(String key, 
                                                    FacesMessage.Severity severity) {
        ResourceBundle bundle = getBundle();
        String summary = getStringSafely(bundle, key, null);
        String detail = getStringSafely(bundle, key + "_detail", summary);
        FacesMessage message = new FacesMessage(summary, detail);
        message.setSeverity(severity);
        return message;
    }

    /**This method will retrieve a message from the bundle and populate the
     * message with the given String arguments.
     * @param key The message key
     * @param severity The Severity level.
     * @param args The Object arguments in an object array.
     * @return The FacesMessage formated with the arguments
     * @author Prashan
     */
    public static FacesMessage getMessageFromBundleWithArgs(String key, 
                                                            FacesMessage.Severity severity, 
                                                            Object[] args) {
        ResourceBundle bundle = getBundle();
        String summary = getStringSafely(bundle, key, null);
        summary = MessageFormat.format(summary, args);
        String detail = getStringSafely(bundle, key + "_detail", summary);
        FacesMessage message = new FacesMessage(summary, detail);
        message.setSeverity(severity);
        return message;
    }

    public static void addFacesInfoMessage(String key) {
    	FacesContext facesContext = FacesContext.getCurrentInstance();
        facesContext.addMessage(null, getMessageFromBundle(key, FacesMessage.SEVERITY_INFO));
    }
    
    public static void addFacesInfoMessage(String key, Object[] args) {
    	FacesContext facesContext = FacesContext.getCurrentInstance();
        facesContext.addMessage(null, getMessageFromBundleWithArgs(key, FacesMessage.SEVERITY_INFO, args));
    }
    
    public static void addFacesErrorMessage(String key) {
    	FacesContext facesContext = FacesContext.getCurrentInstance();
        facesContext.addMessage(null, getMessageFromBundle(key, FacesMessage.SEVERITY_ERROR));
    }
    
    public static void addFacesErrorMessage(String key, Object[] args) {
    	FacesContext facesContext = FacesContext.getCurrentInstance();
        facesContext.addMessage(null, getMessageFromBundleWithArgs(key, FacesMessage.SEVERITY_ERROR, args));
    }
    
    public static void addFacesErrorMessage(UIComponent component, String key) {
    	FacesContext facesContext = FacesContext.getCurrentInstance();
        facesContext.addMessage(component.getClientId(facesContext), getMessageFromBundle(key, FacesMessage.SEVERITY_ERROR));
    }
    
    public static void addFacesErrorMessage(UIComponent component, String key, Object[] args) {
    	FacesContext facesContext = FacesContext.getCurrentInstance();
        facesContext.addMessage(component.getClientId(facesContext), getMessageFromBundleWithArgs(key, FacesMessage.SEVERITY_ERROR, args));
    }

    public static void addFacesErrorMessage(String attrName, String msg) {
        // TODO: Need a way to associate attribute specific messages
        //       with the UIComponent's Id! For now, just using the view id.
        //TODO: make this use the internal getMessageFromBundle?
        FacesMessage fm = 
            new FacesMessage(FacesMessage.SEVERITY_ERROR, attrName, msg);
        context().addMessage(getRootViewComponentId(), fm);
    }

    // Informational getters

    public static String getRootViewId() {
        return context().getViewRoot().getViewId();
    }

    public static String getRootViewComponentId() {
        return context().getViewRoot().getId();
    }


    /*
   * Internal method to pull out the correct local
   * message bundle
   */

    private static ResourceBundle getBundle() {
        UIViewRoot uiRoot = context().getViewRoot();
        Locale locale = uiRoot.getLocale();
        ClassLoader ldr = Thread.currentThread().getContextClassLoader();
        return ResourceBundle.getBundle(context().getApplication().getMessageBundle(), 
                                        locale, ldr);
    }

    /*
       * Internal method to proxy for resource keys that don't exist
       */

    private static String getStringSafely(ResourceBundle bundle, String key, 
                                          String defaultValue) {
        String resource = null;
        try {
            resource = bundle.getString(key);
        } catch (MissingResourceException mrex) {
            if (defaultValue != null) {
                resource = defaultValue;
            } else {
                resource = NO_RESOURCE_FOUND + key;
            }
        }
        return resource;
    }

    /**
     * Method to return a property from a property file
     * @param bundleName
     * @param key
     * @return
     */
    public static String getStringFromBundle(String bundleName, String key){
        String value = key;
        ResourceBundle resourceBundle = ResourceBundle.getBundle(bundleName);
        value = resourceBundle.getString(key);
        return value;
    }
    
    public static String getStringFromBundle(String bundleName, String key, Object[] args){
        return MessageFormat.format(getStringFromBundle(bundleName, key), args);
    }
    
    /**
     * Convenience method for getting Session variables.
     * @param key object key
     */
    public static Object getRequestParameter(String key){
            return context().getExternalContext().getRequestParameterMap().get(key);
    }
    
    /**
     * Convenience method for getting Session variables. Please make sure to call this.removeFromSession
     * method once session variable is used.
     * @param key object key
     */
    @SuppressWarnings("unchecked")
	public static Object getFromSession(String key) {
        Map sessionState = context().getExternalContext().getSessionMap();
        return sessionState.get(key);
    }
    
    /**
     * Method to return the selected object by Id
     * @param id
     * @return selected object
     */
    public static Object getSelectedObjectByID(String id) {
		Object obj = context().getExternalContext().getRequestMap().get(id);
		return (obj);
	}
    
	/**
	 * Invoke a managed bean method.
	 */
	public static void invokeManageBeanActionListernerMethod(String backingBeanAlias, String methodName,ActionEvent e) {
		
		try {
		    getManagedBeanValue(backingBeanAlias).getClass().getMethod(methodName, new Class[]{javax.faces.event.ActionEvent.class})
		    	.invoke(getManagedBeanValue(backingBeanAlias), new Object[]{e});
        } catch (Throwable t){
        	throw new AppUncheckedException(t);
        }
	}
	
	/**
	 * Invoke a managed bean action method.
	 */
	public static String invokeManageBeanActionMethod(String backingBeanAlias, String methodName) {
		
		try {
			Object managedBean = getManagedBeanValue(backingBeanAlias);
		    return (String) managedBean.getClass().getMethod(methodName).invoke(managedBean);
        } catch (Throwable t){
        	throw new AppUncheckedException(t);
        }
	}

	/**
	 * Check Managed Bean already exists.
	 */
	public static boolean isManagedBeanExists(String backingBeanAlias) {
		return FacesContext.getCurrentInstance().getExternalContext().getRequestMap().containsKey(backingBeanAlias);	
	}
	
	public static void updateValues(UIComponent component, FacesContext fc) {
		if (component == null) {
			return;
		}
		
		List<UIComponent> list = component.getChildren();
		for (UIComponent child : list) {
			updateValues(child, fc);
		}
		
		if (component instanceof UIInput) {
			UIInput input = (UIInput) component;
			input.updateModel(fc);
		}
	}

}
